#include <glog/logging.h>

#include "doc/index_doc_field_convertor.h"
#include "util/hashmap/hash_algorithm.h"

namespace hawking {
namespace indexlib {

std::shared_ptr<AttributeConvertor> CreateMultiAttrConvertor(const FieldConfig& field_config) {
    switch (field_config.field_type())
    {
    case util::COLUMN_STRING_LIST: 
        return std::make_shared<MultiStringAttributeConvertor>(field_config.field_type());
    case util::COLUMN_INT8_LIST:
        return std::make_shared<MultiValueAttributeConvertor<int8_t>>(field_config.field_type());
    case util::COLUMN_INT16_LIST:
        return std::make_shared<MultiValueAttributeConvertor<int16_t>>(field_config.field_type());
    case util::COLUMN_INT32_LIST:
        return std::make_shared<MultiValueAttributeConvertor<int32_t>>(field_config.field_type());
    case util::COLUMN_INT64_LIST:
        return std::make_shared<MultiValueAttributeConvertor<int64_t>>(field_config.field_type());
    case util::COLUMN_FLOAT_LIST:
        return std::make_shared<MultiValueAttributeConvertor<float>>(field_config.field_type());
    case util::COLUMN_DOUBLE_LIST:
        return std::make_shared<MultiValueAttributeConvertor<double>>(field_config.field_type());
    default:
        break;
    }

    return nullptr;
}

std::shared_ptr<AttributeConvertor> CreateSingleAttrConvertor(const FieldConfig& field_config) {
    switch (field_config.field_type())
    {
    case util::COLUMN_STRING:
        return std::make_shared<StringAttributeConvertor>(field_config.field_type());
    case util::COLUMN_INT8:
        return std::make_shared<SingleValueAttributeConvertor<int8_t>>(field_config.field_type());
    case util::COLUMN_INT16:
        return std::make_shared<SingleValueAttributeConvertor<int16_t>>(field_config.field_type());
    case util::COLUMN_INT32:
        return std::make_shared<SingleValueAttributeConvertor<int32_t>>(field_config.field_type());
    case util::COLUMN_INT64:
        return std::make_shared<SingleValueAttributeConvertor<int64_t>>(field_config.field_type());
    case util::COLUMN_FLOAT:
        return std::make_shared<SingleValueAttributeConvertor<float>>(field_config.field_type());
    case util::COLUMN_DOUBLE:
        return std::make_shared<SingleValueAttributeConvertor<double>>(field_config.field_type());
    default:
        break;
    }

    return nullptr;
}

void IndexDocFieldConvertor::Init_(std::shared_ptr<IndexlibIndexConfig> index_config) {
    const auto& field_configs = index_config->fields();
    attr_convertors_.resize(field_configs.size());

    for (int32_t idx = 0; idx < field_configs.size(); ++idx) {
        const auto& field_config = field_configs[idx];
        if (field_config.field_type() >= util::COLUMN_INT8_LIST &&
            field_config.field_type() <= util::COLUMN_STRING_LIST) {
            attr_convertors_[field_config.field_id()] = CreateMultiAttrConvertor(field_config);
        } else {
            attr_convertors_[field_config.field_id()] = CreateSingleAttrConvertor(field_config);
        }
    }
}

// no matter collide, different field has own unique term-key-list
bool GetFieldHash(
    const FieldConfig& field_config, const util::FieldValue* field_value, uint64_t* term) {
    std::string str;
    switch (field_config.field_type())
    {
    case util::COLUMN_INT8:
    case util::COLUMN_INT16:
    case util::COLUMN_INT32:
    case util::COLUMN_INT64:
        str = std::to_string(field_value->int_value());
        *term = HashAlgorithm::HashString64(str.data(), str.length());
        return true;
    case util::COLUMN_FLOAT:
    case util::COLUMN_DOUBLE:
        str = std::to_string(field_value->double_value());
        *term = HashAlgorithm::HashString64(str.data(), str.length());
        return true;
    case util::COLUMN_STRING:
        *term = HashAlgorithm::HashString64(
            field_value->bytes_value().data(),
            field_value->bytes_value().length());
        return true;
    default:
        break;
    }

    return false;
}

template<class T>
void GetFieldHash(T field_value, uint64_t* term) {
    std::string str = std::to_string(field_value);
    *term = HashAlgorithm::HashString64(str.data(), str.length());
    return true;
}

bool IndexDocFieldConvertor::ConvertIndexField(
    std::shared_ptr<NormalDocument> doc, const FieldConfig& field_config,
    const util::FieldValue* field_value) {
    if (!field_value) {
        return false;
    }

    uint64_t token_hash;
    if (!GetFieldHash(field_config, field_value, &token_hash)) {
        return false;
    }

    doc->GetIndexDocument()->CreateField(field_config.field_id(), token_hash);
    return true;
}

bool IndexDocFieldConvertor::ConvertAttributeField(
    std::shared_ptr<NormalDocument> doc, 
    int32_t field_id,
    const FieldConfig& field_config,
    const util::FieldValue* field_value) {
    if (field_id >= attr_convertors_.size()) {
        LOG(ERROR) << "field_name " << field_config.field_name()
            << " with field_id " << field_id << " exceed invalid";
        return false;
    }

    std::shared_ptr<AttributeConvertor> attr_convertor = attr_convertors_[field_id];
    if (field_value) {
        std::string_view encoded_value =
            attr_convertor->Encode(field_value, doc->GetPool());
        doc->GetAttributeDocument()->SetField(field_id, encoded_value);
    } else {
        std::string_view default_value =
            attr_convertor->Encode(&field_config.default_value(), doc->GetPool());
        doc->GetAttributeDocument()->SetField(field_id, default_value);
    }

    return true;
}

}
}